Popular Searches
Popular Course Categories
Popular Courses

Implementing Inheritance and Method Overriding

Implementing Inheritance and Method Overriding

Object-Oriented Kotlin and Advanced Language Features

Implementing Inheritance and Method Overriding in Kotlin

Course: Android App Development

Topic: Implementing Inheritance and Method Overriding

Course Link: Android App Development Course

Demo Link: Register for a Course Demo

Website: JustAcademy


1. Introduction to Inheritance

Inheritance is a fundamental concept of object-oriented programming (OOP) that allows one class to acquire accessible properties and functions from another class. In Kotlin, inheritance helps developers reuse common functionality, establish relationships between classes, and customize behavior for different types of objects.

The class providing shared functionality is called the parent class, base class, or superclass. The class that inherits from it is called the child class, derived class, or subclass.

For example, an Android application might have a general Employee class containing a name and a method for displaying employee information. Classes such as Developer and Designer can inherit those features and introduce their own specialized behavior.

Benefits of Inheritance

  • Code reusability: Common properties and functions can be defined once in a parent class.
  • Extensibility: Child classes can add specialized properties and functions.
  • Maintainability: Shared logic can be managed in one place.
  • Polymorphism: Different subclasses can respond differently to the same function call.
  • Logical organization: Related classes can be organized into meaningful hierarchies.
  • Framework integration: Android development frequently uses inheritance when extending framework classes such as activities, fragments, and custom views.

2. Understanding Parent and Child Classes

A parent class defines common functionality, while a child class inherits from it and can introduce additional features. In Kotlin, a class must be marked with open or abstract when it is intended to be inherited from. Classes are final by default.

Basic Example

open class Animal {
    fun eat() {
        println("Animal is eating")
    }
}

class Dog : Animal() {
    fun bark() {
        println("Dog is barking")
    }
}

fun main() {
    val dog = Dog()
    dog.eat()
    dog.bark()
}

Output:

Animal is eating
Dog is barking

Explanation

  1. Animal is declared using the open keyword, allowing other classes to inherit from it.
  2. Dog : Animal() declares that Dog inherits from Animal.
  3. The parentheses after Animal call the parent class constructor.
  4. The Dog class inherits the accessible eat() function.
  5. The Dog class defines its own bark() function.

The child class can use inherited members without redefining them. This is useful when several classes share common functionality.

3. Implementing Inheritance with Constructors

A parent class may require constructor parameters to initialize its properties. When a child class inherits from that parent, it must provide the required arguments to the parent constructor.

Example: Employee and Developer

open class Employee(
    val name: String,
    val employeeId: Int
) {
    fun displayDetails() {
        println("Name: $name")
        println("Employee ID: $employeeId")
    }
}

class Developer(
    name: String,
    employeeId: Int,
    val programmingLanguage: String
) : Employee(name, employeeId) {

    fun displaySkill() {
        println("Programming Language: $programmingLanguage")
    }
}

fun main() {
    val developer = Developer(
        "Aarav",
        101,
        "Kotlin"
    )

    developer.displayDetails()
    developer.displaySkill()
}

Output:

Name: Aarav
Employee ID: 101
Programming Language: Kotlin

Key point: The child constructor passes name and employeeId to the parent constructor. The child class then adds its own programmingLanguage property.

4. What Is Method Overriding?

Method overriding occurs when a child class provides its own implementation of an inherited function that has been explicitly allowed to be overridden. It lets subclasses customize behavior while retaining the common structure of the parent class.

In Kotlin, the parent function must be declared with open or abstract, and the child implementation must use the override keyword.

Why Method Overriding Is Useful

  • It allows different subclasses to perform different actions through a common function.
  • It supports runtime polymorphism.
  • It enables specialized behavior without duplicating an entire parent class.
  • It helps Android components customize framework-provided behavior.
  • It makes code easier to extend when new subclasses are introduced.

5. Implementing Method Overriding in Kotlin

Example: Animal Sounds

open class Animal {
    open fun makeSound() {
        println("Animal makes a sound")
    }
}

class Dog : Animal() {
    override fun makeSound() {
        println("Dog barks")
    }
}

class Cat : Animal() {
    override fun makeSound() {
        println("Cat meows")
    }
}

fun main() {
    val animal = Animal()
    val dog = Dog()
    val cat = Cat()

    animal.makeSound()
    dog.makeSound()
    cat.makeSound()
}

Output:

Animal makes a sound
Dog barks
Cat meows

Step-by-Step Explanation

  1. The Animal class is declared as open so that it can be inherited.
  2. The makeSound() function is also declared as open, allowing subclasses to replace its implementation.
  3. The Dog class overrides makeSound() to print a dog-specific message.
  4. The Cat class overrides the same function to print a cat-specific message.
  5. Calling the function on each object executes the implementation appropriate to that object.

Notice that the parent class and both child classes use the same function name. The actual behavior changes according to the object's runtime type.

6. Understanding the open and override Keywords

Keyword Purpose Example
open Allows a class or member to be inherited or overridden. open class Vehicle
override Provides a new implementation of an inherited overridable member. override fun start()
final Prevents further overriding or inheritance where applicable. final override fun start()
abstract Declares a class or member that requires implementation by a concrete subclass. abstract fun calculate()

Important Rules

  • A normal Kotlin class cannot be inherited from unless it is declared open or abstract.
  • A normal member function cannot be overridden unless it is declared open or abstract, or is itself an eligible override.
  • The override keyword is mandatory when overriding a member.
  • An overridden member remains open for further overriding unless it is marked final.
  • A class can inherit from one class but can implement multiple interfaces.
  • Private members are not directly accessible to subclasses and cannot be overridden as inherited members.

7. Calling Parent Methods with super

The super keyword allows a child class to call an implementation provided by its parent class. This is helpful when the child needs to preserve the parent's behavior and then add its own functionality.

Example

open class Vehicle {
    open fun start() {
        println("Vehicle is starting")
    }
}

class Car : Vehicle() {
    override fun start() {
        super.start()
        println("Car engine is ready")
    }
}

fun main() {
    val car = Car()
    car.start()
}

Output:

Vehicle is starting
Car engine is ready

The call to super.start() executes the parent implementation first. The child then performs its additional operation.

When to Use super

  • When extending existing behavior instead of completely replacing it.
  • When a framework method must perform its normal setup before custom logic runs.
  • When maintaining shared behavior across subclasses.
  • When calling a specific parent implementation to resolve an inherited behavior conflict.

8. Method Overriding and Runtime Polymorphism

Polymorphism means that a common type can represent objects with different implementations. When an overridden function is called through a parent-class reference, Kotlin executes the implementation belonging to the actual object.

Example

open class Notification {
    open fun send() {
        println("Sending a general notification")
    }
}

class EmailNotification : Notification() {
    override fun send() {
        println("Sending an email notification")
    }
}

class SmsNotification : Notification() {
    override fun send() {
        println("Sending an SMS notification")
    }
}

fun sendNotification(notification: Notification) {
    notification.send()
}

fun main() {
    sendNotification(EmailNotification())
    sendNotification(SmsNotification())
}

Output:

Sending an email notification
Sending an SMS notification

The function sendNotification() accepts the parent type Notification. It does not need separate versions for email and SMS. The appropriate implementation is selected at runtime.

Benefits of Runtime Polymorphism

  • Reduces duplicate code.
  • Supports extensible application design.
  • Allows functions to work with multiple related object types.
  • Helps developers add new implementations without rewriting existing calling logic.

9. Overriding Properties in Kotlin

Kotlin allows eligible properties to be overridden as well as functions. A parent property must be declared as open or abstract to be overridden by a subclass.

Example

open class Shape {
    open val numberOfSides: Int = 0
}

class Triangle : Shape() {
    override val numberOfSides: Int = 3
}

class Square : Shape() {
    override val numberOfSides: Int = 4
}

fun main() {
    println(Triangle().numberOfSides)
    println(Square().numberOfSides)
}

Output:

3
4

A read-only val property can be overridden by a var property because the latter provides a getter as well as a setter. However, a mutable var property cannot be overridden by a read-only val property because that would remove the parent's setter capability.

10. Abstract Classes and Method Overriding

An abstract class provides a shared base for related classes but cannot be instantiated directly. It may contain implemented functions as well as abstract functions that have no implementation and must be implemented by a concrete subclass.

Example

abstract class Payment {
    abstract fun processPayment(amount: Double)

    fun printReceipt() {
        println("Payment processing completed")
    }
}

class CardPayment : Payment() {
    override fun processPayment(amount: Double) {
        println("Processing card payment of $amount")
    }
}

class UpiPayment : Payment() {
    override fun processPayment(amount: Double) {
        println("Processing UPI payment of $amount")
    }
}

fun main() {
    val card = CardPayment()
    card.processPayment(1500.0)
    card.printReceipt()

    val upi = UpiPayment()
    upi.processPayment(500.0)
}

Output:

Processing card payment of 1500.0
Payment processing completed
Processing UPI payment of 500.0

The abstract class defines a common payment contract. Each concrete payment class supplies its own implementation of processPayment().

11. Inheritance with Multiple Interfaces

Kotlin supports single inheritance from a class but allows a class to implement multiple interfaces. Interfaces can define functions without implementations and can also provide default function implementations.

Example

interface Camera {
    fun takePhoto()
}

interface MusicPlayer {
    fun playMusic()
}

class SmartPhone : Camera, MusicPlayer {
    override fun takePhoto() {
        println("Photo captured")
    }

    override fun playMusic() {
        println("Music is playing")
    }
}

fun main() {
    val phone = SmartPhone()
    phone.takePhoto()
    phone.playMusic()
}

Output:

Photo captured
Music is playing

Interfaces are useful when a class needs to provide several capabilities without inheriting implementation from multiple classes.

Resolving Conflicting Interface Implementations

If two interfaces provide implementations of the same function, the implementing class must override the function and resolve the conflict explicitly.

interface FirstAction {
    fun execute() {
        println("First action")
    }
}

interface SecondAction {
    fun execute() {
        println("Second action")
    }
}

class ActionHandler : FirstAction, SecondAction {
    override fun execute() {
        super.execute()
        super.execute()
        println("Combined action")
    }
}

The qualified super expressions specify which interface implementations should be called.

12. Practical Android Example: Customizing Activity Behavior

Android development uses inheritance when an application creates an activity by extending the Android framework's Activity or ComponentActivity classes. Framework lifecycle methods can be overridden to add application-specific behavior.

Example with ComponentActivity

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Log.d("MainActivity", "Activity created")
    }
}

Explanation

  • MainActivity inherits from ComponentActivity.
  • onCreate() is overridden to add custom activity initialization logic.
  • super.onCreate(savedInstanceState) calls the superclass implementation.
  • The logging statement records a message when the activity is created.

This example demonstrates the general inheritance pattern used in Android. Lifecycle callbacks should be overridden according to the framework contract, and required superclass calls must be preserved where the Android API requires them.

13. Practical Android Example: Notification Types

Suppose an Android application supports email, SMS, and in-app notifications. Inheritance and method overriding can provide a shared notification abstraction while allowing each type to define its own behavior.

abstract class AppNotification {
    abstract fun deliver()

    fun logDelivery() {
        println("Notification delivery requested")
    }
}

class EmailAlert : AppNotification() {
    override fun deliver() {
        println("Delivering email alert")
    }
}

class InAppAlert : AppNotification() {
    override fun deliver() {
        println("Displaying in-app alert")
    }
}

fun processAlert(alert: AppNotification) {
    alert.logDelivery()
    alert.deliver()
}

fun main() {
    processAlert(EmailAlert())
    processAlert(InAppAlert())
}

Output:

Notification delivery requested
Delivering email alert
Notification delivery requested
Displaying in-app alert

This design separates shared notification logic from delivery-specific behavior. In a real application, notification delivery should also account for permissions, lifecycle state, network errors, and platform requirements.

14. Difference Between Inheritance and Method Overriding

Feature Inheritance Method Overriding
Meaning A class inherits accessible members from a parent class. A child class replaces an inherited overridable function implementation.
Primary purpose Reuse and extend common functionality. Customize inherited behavior.
Required syntax Child class declaration with :. override on the child member.
Parent requirement Class must be inheritable. Function must be overridable or abstract.
New function required? No; inherited functions can be reused. Yes; the child supplies a replacement implementation.
Example class Dog : Animal() override fun makeSound()

15. Overriding Versus Function Overloading

Overriding and overloading are different concepts. Overriding changes an inherited function's implementation in a subclass. Overloading defines multiple functions with the same name but different parameter lists, usually within the same class or related scope.

Feature Overriding Overloading
Relationship Requires an inherited member. Does not require inheritance.
Function signature Matches the overridden function's signature. Parameter lists differ.
Keyword Uses override. No special keyword required.
Purpose Customize inherited behavior. Offer multiple ways to call a function.

Overloading Example

class Calculator {
    fun add(a: Int, b: Int): Int {
        return a + b
    }

    fun add(a: Double, b: Double): Double {
        return a + b
    }
}

fun main() {
    val calculator = Calculator()
    println(calculator.add(10, 20))
    println(calculator.add(2.5, 3.5))
}

Output:

30
6.0

16. Common Inheritance and Overriding Errors

Error Reason Solution
Cannot inherit from a final class The parent class is not declared open or abstract. Mark the class open if inheritance is intended.
Function cannot be overridden The parent function is not overridable. Declare it open or abstract.
Missing override modifier Kotlin requires explicit overriding. Add the override keyword.
No value passed to parent constructor The parent requires constructor arguments. Pass the required arguments in the subclass declaration.
Conflicting interface implementations Multiple interfaces provide matching implementations. Override the member and resolve the conflict explicitly.
Superclass method behavior is missing The child replaces the implementation without calling the parent. Call super.methodName() when parent behavior is required.
Incorrect initialization behavior Base-class initialization accesses members that are not yet initialized in the child. Avoid calling overridable members from constructors, initializers, or initialization blocks.

17. Best Practices for Inheritance and Method Overriding

  • Use inheritance when classes genuinely represent an “is-a” relationship.
  • Keep parent classes focused on common functionality.
  • Mark classes and functions open only when extension is intended.
  • Use override to make customization explicit.
  • Call super when the parent implementation is part of the required behavior.
  • Use abstract classes when related subclasses must implement common operations.
  • Prefer interfaces when you need to describe capabilities that can be combined.
  • Avoid excessively deep inheritance hierarchies because they can make code harder to understand.
  • Do not call overridable functions from constructors or initialization blocks when they may access uninitialized subclass state.
  • Consider composition instead of inheritance when a class merely needs to use another object's functionality.
  • Test each subclass independently and through its parent type.
  • Keep overridden functions consistent with the expectations established by the parent class.

18. Practical Project: Employee Management System

Objective: Create a simple employee management model that demonstrates inheritance, method overriding, constructors, and runtime polymorphism.

Project Requirements

  • Create an open parent class named Employee.
  • Store the employee's name and basic salary.
  • Create a function named calculateBonus() that can be overridden.
  • Create Developer and Manager subclasses.
  • Provide a different bonus calculation for each subclass.
  • Use a common function to display the bonus of different employee types.

Sample Implementation

open class Employee(
    val name: String,
    val salary: Double
) {
    open fun calculateBonus(): Double {
        return salary * 0.05
    }

    fun displayBonus() {
        println(
            "$name's bonus: ${calculateBonus()}"
        )
    }
}

class Developer(
    name: String,
    salary: Double
) : Employee(name, salary) {
    override fun calculateBonus(): Double {
        return salary * 0.10
    }
}

class Manager(
    name: String,
    salary: Double
) : Employee(name, salary) {
    override fun calculateBonus(): Double {
        return salary * 0.15
    }
}

fun main() {
    val employees: List<Employee> = listOf(
        Employee("Aman", 30000.0),
        Developer("Priya", 50000.0),
        Manager("Rahul", 70000.0)
    )

    employees.forEach {
        it.displayBonus()
    }
}

Expected Output:

Aman's bonus: 1500.0
Priya's bonus: 5000.0
Rahul's bonus: 10500.0

Learning outcome: The common displayBonus() function calls the overridden calculateBonus() implementation appropriate to each employee object. This illustrates inheritance and runtime polymorphism together.

Note: The bonus percentages are illustrative values for learning purposes, not real payroll rules.

19. Practice Exercises

  1. Create a parent class Vehicle with a function named start(). Create Car and Bike subclasses that override it.
  2. Create a parent class Shape and override a function named calculateArea() in Circle and Rectangle.
  3. Create a class Notification with an overridable send() function. Implement email and SMS notification subclasses.
  4. Create an abstract class Account with an abstract function named calculateInterest(). Implement it in savings and fixed-deposit account classes.
  5. Modify an overridden function to call its parent implementation using super.
  6. Create a list of parent-class references containing multiple subclass objects and call their overridden functions in a loop.
  7. Try inheriting from a class without using open and observe the compiler error. Correct it by applying the appropriate modifier.

20. Interview Questions and Answers

Q1. What is inheritance in Kotlin?

Answer: Inheritance allows a child class to reuse and extend accessible properties and functions defined in a parent class.

Q2. Why must a Kotlin class be declared open for inheritance?

Answer: Kotlin classes are final by default. The open modifier explicitly permits a class to be inherited from.

Q3. What is method overriding?

Answer: Method overriding occurs when a subclass provides its own implementation of an inherited function that permits overriding.

Q4. What is the purpose of the override keyword?

Answer: It explicitly identifies a member as an implementation that overrides an inherited member.

Q5. What is the difference between open and override?

Answer: open permits inheritance or further overriding. override supplies a replacement implementation for an inherited member.

Q6. What does the super keyword do?

Answer: It accesses an implementation or property accessor from a superclass or, when qualified, a specific interface implementation.

Q7. What is runtime polymorphism?

Answer: Runtime polymorphism means an overridden function call is dispatched to the implementation associated with the actual object type.

Q8. Can Kotlin inherit from multiple classes?

Answer: No. A class can directly inherit from only one class, but it can implement multiple interfaces.

Q9. Can properties be overridden in Kotlin?

Answer: Yes. An eligible property declared with open or abstract can be overridden with a compatible property declaration.

Q10. What is an abstract class?

Answer: An abstract class cannot be instantiated directly and may declare abstract members that concrete subclasses must implement.

Q11. Can an overridden function be overridden again?

Answer: Yes. An override remains open by default unless it is marked final.

Q12. What is the difference between overriding and overloading?

Answer: Overriding customizes an inherited function with a matching signature, whereas overloading defines functions with the same name but different parameter lists.

Q13. Why is inheritance useful in Android development?

Answer: It allows developers to extend framework classes and customize lifecycle callbacks, custom view behavior, and other supported extension points.

Q14. What happens if a subclass does not implement an inherited abstract function?

Answer: A concrete subclass must implement inherited abstract members. Otherwise, the declaration is invalid. An abstract subclass may leave them abstract.

Q15. When should composition be preferred over inheritance?

Answer: Composition is often preferable when a class needs to use another object's functionality without representing a direct “is-a” relationship.

21. Quick Revision Checklist

  • Understand parent classes and child classes.
  • Use open to allow class inheritance.
  • Use the colon syntax to declare a subclass.
  • Pass required arguments to the parent constructor.
  • Use open or abstract for overridable parent functions.
  • Use override in the child implementation.
  • Use super to access parent behavior when needed.
  • Understand runtime polymorphism.
  • Differentiate method overriding from overloading.
  • Practice abstract classes and multiple interfaces.
  • Recognize Android framework methods that are intended to be overridden.
  • Test inheritance hierarchies and avoid unnecessary complexity.

22. Additional Learning Resources

23. Conclusion

Inheritance and method overriding are essential object-oriented programming concepts for Kotlin and Android development. Inheritance allows classes to reuse and extend shared functionality, while overriding allows subclasses to customize the behavior of inherited functions and properties.

By understanding the open, override, super, and abstract keywords, developers can create flexible class hierarchies, implement runtime polymorphism, and extend Android framework components correctly. Practice with vehicles, employees, notifications, and Android activities to build a strong foundation for developing maintainable Android applications.

Continue Learning: Android App Development Course | Book a Course Demo | JustAcademy

Previous Next
whatsapp